# -*- coding: utf-8 -*-

# Podboy -- A podcast aggregator/player
#
# Copyright (C) 2009-2012 Valéry Febvre <vfebvre@easter-eggs.com>
# http://code.google.com/p/podboy/
#
# This file is part of Podboy.
#
# Podboy is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# Podboy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import pygst
pygst.require('0.10')
import gst

class PodboyPlayer(object):
    """
    Class to contain all the junk we need to play various formats in GStreamer.
    """

    def __init__(self):
        self.pipeline  = None
        self.source  = None
        # One decoder for each media type
        self.decoders = []
        # One muxer for each media type
        self.demuxers = []
        # A list of pipeline elements for each output type
        self.elements = {'normal': [], 'bluetooth': []}

        self.media_type = MediaTypes.NONE
        self.output_type = None
        self.time_format = gst.Format(gst.FORMAT_TIME)

    def init(self):
        """
        Initialize all the GStreamer pipeline elements for all media types.
        """
        import gobject
        gobject.threads_init()

        self.pipeline = gst.Pipeline("PodboyPlayer")

        self.source = gst.element_factory_make("filesrc", "filesrc")
        #self.caps = gst.element_factory_make("capsfilter", "capsfilter")
        #self.caps.set_property('caps', gst.caps_from_string('audio/x-raw-int,rate=44100'))

        # MP3
        self.decoders.append(gst.element_factory_make("mad", "decode-mp3"))
        self.demuxers.append(None)

        # OGG
        self.decoders.append(gst.element_factory_make("vorbisdec", "decode-ogg"))
        self.demuxers.append(gst.element_factory_make("oggdemux", "demux-vorbis"))
        self.demuxers[MediaTypes.OGG].connect("pad-added", self.ogg_pad_added_cb)

        # normal: standart output
        self.elements['normal'].append(gst.element_factory_make("audioconvert", "audioconvert"))
        self.elements['normal'].append(gst.element_factory_make("audioresample", "audioresample"))
        #self.elements['normal'].append(self.caps)
        self.elements['normal'].append(gst.element_factory_make("volume", "volume"))
        self.elements['normal'].append(gst.element_factory_make("alsasink", "output"))

        # bluetooth
        self.elements['bluetooth'].append(gst.element_factory_make("audioconvert", "audioconvert"))
        self.elements['bluetooth'].append(gst.element_factory_make("audioresample", "audioresample"))
        #self.elements['bluetooth'].append(self.caps)
        self.elements['bluetooth'].append(gst.element_factory_make("volume", "volume"))
        self.elements['bluetooth'].append(gst.element_factory_make("sbcenc", "sbcenc"))
        self.elements['bluetooth'].append(gst.element_factory_make("a2dpsink", "a2dpsink"))

    def set_pipeline(self, media_type, output_type, bluetooth_device_address = None):
        """
        Prepares the player to play the given type of media.
        Removes the current pipeline and replaces it with a new one if the media type is different
        than the current media type.
        \param media_type Media type to play. One of MediaTypes.
        \param output_type Audio output. 'normal' or 'bluetooth'
        \param bluetooth_device_address Address of Bluetooth device if output_type is 'bluetooth'
        """
        if bluetooth_device_address and output_type == 'bluetooth':
            self.elements['bluetooth'][-1].set_property('device', bluetooth_device_address)

        # No change needed
        if self.media_type == media_type and self.output_type == output_type:
            return

        # First remove the current pipeline
        # Do not do this the first time through
        if self.media_type != MediaTypes.NONE:
            self.pipeline.remove(self.source, *self.elements[self.output_type])
            if self.media_type == MediaTypes.OGG:
                self.pipeline.remove(self.decoders[MediaTypes.OGG], self.demuxers[MediaTypes.OGG])
            elif self.media_type == MediaTypes.MP3:
                self.pipeline.remove(self.decoders[MediaTypes.MP3])

        # Next, build the correct pipeline
        if media_type == MediaTypes.MP3:
            self.pipeline.add(self.source, self.decoders[MediaTypes.MP3], *self.elements[output_type])
            gst.element_link_many(self.source, self.decoders[MediaTypes.MP3], *self.elements[output_type])
        elif media_type == MediaTypes.OGG:
            self.pipeline.add(
                self.source, self.demuxers[MediaTypes.OGG], self.decoders[MediaTypes.OGG], *self.elements[output_type])
            gst.element_link_many(self.source, self.demuxers[MediaTypes.OGG])
            gst.element_link_many(self.decoders[MediaTypes.OGG], *self.elements[output_type])

        self.media_type = media_type
        self.output_type = output_type

    def ogg_pad_added_cb(self, demuxer, pad):
        adec_pad = self.decoders[MediaTypes.OGG].get_pad("sink")
        if adec_pad and not adec_pad.is_linked():
            pad.link(adec_pad)

    def get_bus(self):
        """
        Returns the GStreamer bus attached to the player.
        """
        return self.pipeline.get_bus()

    def get_duration(self):
        """ 
        Returns the length (float, seconds) of the current track.
        Only works if the player state has been set to Playing.
        """
        try:
            return float(self.pipeline.query_duration(self.time_format, None)[0] / 1000000000)
        except:
            return 0

    def get_position(self):
        """
        Returns the current position (float, seconds) of the current track.
        Only works if the player state has been set to Playing.
        """
        try:
            position = float(self.pipeline.query_position(self.time_format, None)[0] / 1000000000)
            return position
        except:
            return None

    def get_state(self):
        """ 
        Gets the state of the GStreamer player.
        """
        return self.pipeline.get_state()[1]

    @property
    def playing(self):
        state = self.get_state()
        return state == gst.STATE_PLAYING or state == gst.STATE_PAUSED

    def set_file(self, location):
        """
        Sets the file name the player is to play.
        \param location Path to file to load.
        """
        self.source.set_property("location", location)
            
    def set_state(self, state):
        """
        Sets the state in the GStreamer player the given state.
        \param state New state.
        """
        self.pipeline.set_state(state)

    def set_volume(self, volume):
        """
        Set the playback volume of the player.
        \param volume Volume level from 0 to 100
        """
        self.pipeline.get_by_name("volume").set_property("volume", float(volume) / 25)

    def toggle_mute(self):
        val = not self.pipeline.get_by_name("volume").get_property("mute")
        self.pipeline.get_by_name("volume").set_property("mute", val)
        return val

    def play(self):
        self.pipeline.set_state(gst.STATE_PLAYING)

    def pause(self):
        self.pipeline.set_state(gst.STATE_PAUSED)

    def stop(self):
        self.seek(0.1)
        self.pipeline.set_state(gst.STATE_NULL)

    def seek(self, position):
        """
        Seeks the player to the given position in seconds.
        """
        # STRANGENESS: seek can failed, several tries can be necessary
        tries = 0
        done = False
        while not done and tries < 100:
            done = self.pipeline.seek_simple(self.time_format, gst.SEEK_FLAG_FLUSH, position * 1000000000)
            tries += 1


class MediaTypes:
    """
    Supported media types for the PodboyPlayer class.
    """
    NONE = -1
    MP3 = 0
    OGG = 1
    COUNT = 2
